<!doctype html>
<html lang=ru id=faq>

<title>OpenBSD PF: Преобразование сетевых адресов (NAT)</title>
<meta charset=utf-8>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="../../openbsd.css">
<link rel="canonical" href="https://www.openbsd.org/faq/pf/nat.html">
<style>
li p {
	margin: 0;
}
</style>

<h2 id=OpenBSD>
<a href="../../index.html">
<i>Open</i><b>BSD</b></a>
PF - Преобразование сетевых адресов (Network Address Translation)
<small>
<a href="index.html">[FAQ PF - На главную]</a>
</small>
</h2>
<hr>

<ul>
  <li><a href="#intro"  >Введение</a>
  <li><a href="#works"  >Принцип работы NAT</a>
  <li><a href="#ipfwd"  >IP перенаправление</a>
  <li><a href="#config" >Настройка NAT</a>
  <li><a href="#binat"  >Bidirectional Mapping (1:1 Mapping)</a>
  <li><a href="#except" >Translation Rule Exceptions</a>
  <li><a href="#status" >Проверка состояния NAT</a>
</ul>

<hr>

<h2 id="intro">Введение</h2>

Преобразование (или сопоставление) сетевых адресов (Network Address
Translation, NAT) - это способ сопоставить всю сеть (или сети) одному
IP-адресу. NAT необходим, когда количество IP-адресов, предоставленных
вам вашим провайдером, меньше общего количества компьютеров, которым нужен
доступ в интернет.
Стандарт NAT описан в <a href="https://tools.ietf.org/html/rfc1631">RFC 1631</a>.

<p>
NAT позволяет вам воспользоваться зарезервированными адресными блоками,
описанными в <a href="http://tools.ietf.org/html/rfc1918">RFC 1918</a>.
Как правило, ваша внутренняя сеть будет настроена на использование одного
или нескольких из этих сетевых блоков.

<pre class="cmdbox">
10.0.0.0/8       (10.0.0.0 - 10.255.255.255)
172.16.0.0/12    (172.16.0.0 - 172.31.255.255)
192.168.0.0/16   (192.168.0.0 - 192.168.255.255)
</pre>

OpenBSD, используемая для преобразования сетевых адресов (NAT), должна иметь
как минимум два сетевых интерфейса: один для интернета, другой для внутренней
сети. NAT будет транслировать запросы из внутренней сети, поэтому они будут
поступать от вашей OpenBSD системы с NAT.

<h2 id="works">Принцип работы NAT</h2>

Когда клиентская машина во внутренней сети связывается с машиной в
интернете, она отправляет, предназначенные для этой машины, IP-пакеты.
Эти пакеты содержат информацию, необходимую для того, чтобы удаленная
машина получила их.
NAT связывает некоторые части этой информаци:

<ul>
  <li>IP-адрес клиентской машины (например, 192.168.1.35)
  <li>TCP- или UDP-порт, используемый клиентской машиной (например, 2132)
</ul>

Когда пакеты проходят через шлюз NAT (NAT gateway), они изменяются
таким образом, как будто их отправляет сам шлюз.
Он записывает изменения, которые вносит, в свою таблицу состояний,
чтобы в последствии мог а) отменить изменения в возвращаемых пакетах
и б) обеспечить, чтобы возвратные пакеты проходили через firewall
и не блокировались.
Например, могут быть сделаны следующие изменения:

<ul>
  <li>IP-адрес клиента: перезаписывается внешним адресом шлюза
      (например, 198.51.100.1)
  <li>Порт клиента: перезаписывается случайно выбранным, неиспользуемым
      шлюзом портом (например, 53136)
</ul>

Ни внутренняя клиентская машина, ни интернет-хост не знают об этих
изменениях. Для клиента NAT - это просто интернет-шлюз.
Для интернет-хоста пакеты, кажется, приходят непосредственно от самого NAT;
совершенно не известно, что внутренняя клиентская машина вообще существует.

<p>
Когда интернет-хост отвечает на пакеты клиентской внутренней машины, они
будут адресованы внешнему IP-адресу NAT-шлюза (198.51.100.1) через порт
трансляции (53136). Затем NAT шлюз выполнит поиск таблицы состояний, чтобы
определить, соответствуют ли полученные пакеты уже установленному соединению.
Уникальное совпадение будет найдено на основе комбинации IP/порта, которая
сообщает PF, что пакеты принадлежат соединению, инициированному внутренней
(клиентской) машиной 192.168.1.35.
Затем PF внесет противоположные изменения в исходящие пакеты и перенаправит
ответные пакеты клиентской машине во внутренней сети.

<p>
Трансляция ICMP-пакетов происходит аналогичным образом, но естественно без
изменения порта клиента.

<h2 id="ipfwd">IP перенаправление</h2>

Поскольку NAT почти всегда используется на маршрутизаторах (routers) и
сетевых шлюзах (network gateways), возможно, потребуется активировать/разрешить
перенаправление IP-пакетов (IP forwarding), чтобы пакеты могли быть переданы
между сетевыми интерфейсами на OpenBSD машине. IP forwarding включается при
помощи <a href="https://man.openbsd.org/sysctl.2">sysctl(2)</a>:

<pre class="cmdbox">
# <b>sysctl net.inet.ip.forwarding=1</b>
# <b>echo  'net.inet.ip.forwarding=1' >> /etc/sysctl.conf</b>
</pre>

Или, если используется IPv6:

<pre class="cmdbox">
# <b>sysctl net.inet6.ip6.forwarding=1</b>
# <b>echo  'net.inet6.ip6.forwarding=1' >> /etc/sysctl.conf</b>
</pre>

<h2 id="config">Настройка NAT</h2>

NAT is specified as an optional <code>nat-to</code> parameter to an outbound
<code>pass</code> rule.
Often, rather than being set directly on the <code>pass</code> rule, a
<code>match</code> rule is used.
When a packet is selected by a <code>match</code> rule, parameters (e.g.
<code>nat-to</code>) in that rule are remembered and are applied to the packet
when a <code>pass</code> rule matching the packet is reached.
This permits a whole class of packets to be handled by a single
<code>match</code> rule and then specific decisions on whether to allow the
traffic can be made with <code>block</code> and <code>pass</code> rules.

<p>
The general format in <code>pf.conf</code> looks something like this:

<pre class="cmdbox">
match out on interface [af] \
   from src_addr to dst_addr \
   nat-to ext_addr [pool_type] [static-port]
[...]
pass out [log] on interface [af] [proto protocol] \
   from ext_addr [port src_port] \
   to dst_addr [port dst_port]
</pre>

<dl>
<dt><code>match</code>
<dd>When a packet traverses the ruleset and matches a <code>match</code>
    rule, any optional parameters specified in that rule are remembered
    for future use (made "sticky").

<dt><code>pass</code>
<dd>This rule allows the packet to be transmitted.
    If the packet was previously matched by a <code>match</code> rule where
    parameters were specified, they will be applied to this packet.
    <code>pass</code> rules may have their own parameters; these take
    priority over parameters specified in a <code>match</code> rule.

<dt><code>out</code>
<dd>Specifies the direction of packet flow where this rule applies.
    <code>nat-to</code> may only be specified for outbound packets.

<dt><code>log</code>
<dd>Log matching packets via
    <a href="https://man.openbsd.org/pflogd">pflogd(8)</a>.
    Normally, only the first packet that matches will be logged.
    To log all matching packets, use <code>log (all)</code>.

<dt><code><i>interface</i></code>
<dd>The name or group of the network interface to transmit packets on.

<dt><code><i>af</i></code>
<dd>The address family, either <code>inet</code> for IPv4 or <code>inet6</code>
    for IPv6.
    PF is usually able to determine this parameter based on the
    source/destination address(es).

<dt><code><i>protocol</i></code>
<dd>The protocol (e.g. tcp, udp, icmp) of packets to allow.
    If <i>src_port</i> or <i>dst_port</i> is specified, the protocol
    <em>must</em> also be given.

<dt><code><i>src_addr</i></code>
<dd>The source (internal) address of packets that will be translated.
    The source address can be specified as:
    <ul>
      <li>A single IPv4 or IPv6 address.
      <li>A <a href="https://web.archive.org/web/20150213012421/http://public.swbell.net/dedicated/cidr.html">CIDR</a>
          network block.
      <li>A fully qualified domain name that will be resolved via DNS when the
          ruleset is loaded.
          All resulting IP addresses will be substituted into the rule.
      <li>The name or group of a network interface.
          Any IPv4 and IPv6 addresses assigned to the interface will be
          substituted into the rule at load time.
      <li>The name of a network interface followed by
          <code>/<i>netmask</i></code> (e.g. <code>/24</code>).
          Each IP address on the interface is combined with the netmask to
          form a CIDR network block which is substituted into the rule.
      <li>The name or group of a network interface followed by any one of
          these modifiers:
        <ul>
          <li><code>:network</code> - substitutes the CIDR network block (e.g.,
              192.168.0.0/24)
          <li><code>:broadcast</code> - substitutes the network broadcast
          address (e.g., 192.168.0.255)
          <li><code>:peer</code> - substitutes the peer's IP address on a
          point-to-point link
         <p>In addition, the <code>:0</code> modifier can be appended to either
            an interface name/group or to any of the above modifiers to indicate
            that PF should not include aliased IP addresses in the substitution.
            These modifiers can also be used when the interface is contained in
            parentheses.
            Example: <code>fxp0:network:0</code>
        </ul>
      <li>A <a href="tables.html">table</a>.
      <li>Any of the above but negated using the <code>!</code> ("not")
          modifier.
      <li>A set of addresses using a <a href="macros.html#lists">list</a>.
      <li>The keyword <code>any</code> meaning all addresses
    </ul>

<dt><code><i>src_port</i></code>
<dd>The source port in the Layer 4 packet header.
    Ports can be specified as:
    <ul>
      <li>A number between 1 and 65535
      <li>A valid service name from
          <a href="https://man.openbsd.org/services">services(5)</a>
      <li>A set of ports using a <a href="macros.html#lists">list</a>
      <li>A range:
        <ul>
          <li><code>!=</code> (not equal)
          <li><code>&lt;</code> (less than)
          <li><code>&gt;</code> (greater than)
          <li><code>&lt;=</code> (less than or equal)
          <li><code>&gt;=</code> (greater than or equal)
          <li><code>&gt;&lt;</code> (range)
          <li><code>&lt;&gt;</code> (inverse range)
          <p>The last two are binary operators (they take two arguments) and
             do not include the arguments in the range.
          <li><code>:</code> (inclusive range)
          <p>The inclusive range operator is also a binary operator and does
             include the arguments in the range.
    </ul>
</ul>

The <code>port</code> option is not usually used in <code>nat</code> rules
because the goal is usually to NAT all traffic regardless of the port(s) being
used.

<dt><code><i>dst_addr</i></code>
<dd>The destination address of packets to be translated.
    The destination address is specified in the same way as the source address.

<dt><code><i>dst_port</i></code>
<dd>The destination port in the Layer 4 packet header.
    This port is specified in the same way as the source port.

<dt><code><i>ext_addr</i></code>
<dd>The external (translation) address on the NAT gateway that packets
    will be translated to.
    The external address can be specified as:
    <ul>
      <li>A single IPv4 or IPv6 address.
      <li>A <a href="https://web.archive.org/web/20150611113606/http://public.swbell.net/dedicated/cidr.html">CIDR</a>
          network block.
      <li>A fully qualified domain name that will be resolved via DNS when the
          ruleset is loaded.
      <li>The name or group of the external network interface.
          Any IP addresses assigned to the interface will be substituted into
          the rule at load time.
      <li>The name or group of the external network interface in parentheses
          <code>( )</code>.
          This tells PF to update the rule if the IP address(es) on the named
          interface changes.
          This is highly useful when the external interface gets its IP address
          via DHCP or dial-up, as the ruleset doesn't have to be reloaded each
          time the address changes.
      <li>The name or group of a network interface followed by either one of
          these modifiers:
        <ul>
          <li><code>:network</code> - substitutes the CIDR network block (e.g.,
              192.168.0.0/24)
          <li><code>:peer</code> - substitutes the peer's IP address on a
              point-to-point link
         <p>In addition, the <code>:0</code> modifier can be appended to either
            an interface name/group or to any of the above modifiers to indicate
            that PF should not include aliased IP addresses in the substitution.
            These modifiers can also be used when the interface is contained in
            parentheses.
            Example: <code>fxp0:network:0</code>
        </ul>
      <li>A set of addresses using a <a href="macros.html#lists">list</a>.
    </ul>

<dt><code><i>pool_type</i></code>
<dd>Specifies the type of <a href="pools.html">address pool</a> to use
    for translation.

<dt><code>static-port</code>
<dd>Tells PF not to translate the source port in TCP and UDP packets.

</dl>

<p>
This would lead to a most basic form of these lines similar to this:

<pre class="cmdbox">
match out on tl0 from 192.168.1.0/24 to any nat-to 198.51.100.1
pass on tl0 from 192.168.1.0/24 to any
</pre>

Or you may simply use:

<pre class="cmdbox">
pass out on tl0 from 192.168.1.0/24 to any nat-to 198.51.100.1
</pre>

This rule says to perform NAT on the <code>tl0</code> interface for any
packets coming from 192.168.1.0/24 and to replace the source IP address
with 198.51.100.1.

<p>
While the above rule is correct, it is not recommended form.
Maintenance could be difficult as any change of the external or internal
network numbers would require the line be changed.
Compare instead with this easier to maintain line (<code>tl0</code> is external,
<code>dc0</code> internal):

<pre class="cmdbox">
pass out on tl0 inet from dc0:network to any nat-to tl0
</pre>

The advantage should be fairly clear: you can change the IP addresses of
either interface without changing this rule.
Note that <code>inet</code> should be specified in this case to ensure that
only IPv4 addresses are used, avoiding unexpected surprises.

<p>
When specifying an interface name for the translation address as above,
the IP address is determined at pf.conf <i>load</i> time, not on the fly.
If you are using DHCP to configure your external interface, this can be
a problem.
If your assigned IP address changes, NAT will continue translating outgoing
packets using the old IP address.
This will cause outgoing connections to stop functioning.
To get around this, you can tell PF to automatically update the translation
address by putting parentheses around the interface name:

<pre class="cmdbox">
pass out on tl0 inet from dc0:network to any nat-to (tl0)
</pre>

This method works for translation to both IPv4 and IPv6 addresses.

<h2 id="binat">Bidirectional Mapping (1:1 Mapping)</h2>

A bidirectional mapping can be established by using the
<code>binat-to</code> parameter.
A <code>binat-to</code> rule establishes a one to one mapping between an
internal IP address and an external address.
This can be useful, for example, to provide a web server on the internal
network with its own external IP address.
Connections from the Internet to the external address will be translated to
the internal address and connections from the web server (such as DNS requests)
will be translated to the external address.
TCP and UDP ports are never modified with <code>binat-to</code> rules as they
are with <code>nat</code> rules.

<p>
Example:

<pre class="cmdbox">
web_serv_int = "192.168.1.100"
web_serv_ext = "198.51.100.6"

pass on tl0 from $web_serv_int to any binat-to $web_serv_ext
</pre>

<h2 id="except">Translation Rule Exceptions</h2>

If you need to translate most traffic, but provide exceptions in some cases,
make sure that the exceptions are handled by a filter rule which does not
include the nat-to parameter.
For example, if the NAT example above was modified to look like this:

<pre class="cmdbox">
pass  out on tl0 from 192.168.1.0/24 to any nat-to 198.51.100.79
pass  out on tl0 from 192.168.1.208  to any
</pre>

Then the entire 192.168.1.0/24 network would have its packets translated to
the external address 198.51.100.79 except for 192.168.1.208.

<h2 id="status">Проверка состояния NAT</h2>

To view the active NAT translations,
<a href="https://man.openbsd.org/pfctl">pfctl(8)</a> is used with the
<code>-s state</code> option.
This option will list all the current NAT sessions:

<pre class="cmdbox">
# <b>pfctl -s state</b>
fxp0 tcp 192.168.1.35:2132 (198.51.100.1:53136) -&gt; 198.51.100.10:22 TIME_WAIT:TIME_WAIT
fxp0 udp 192.168.1.35:2491 (198.51.100.1:60527) -&gt; 198.51.100.33:53   MULTIPLE:SINGLE
</pre>

Explanations (first line only):

<dl>
<dt>fxp0
<dd>Indicates the interface that the state is bound to.
    The word <code>self</code> will appear if the state is
    <a href="options.html#state-policy"><code>floating</code></a>.
</dl>

<dl>
<dt>TCP
<dd>The protocol being used by the connection.
</dl>

<dl>
<dt>192.168.1.35:2132
<dd>The IP address (192.168.1.35) of the machine on the internal network.
    The source port (2132) is shown after the address.
    This is also the address that is replaced in the IP header.
</dl>

<dl>
<dt>198.51.100.1:53136
<dd>The IP address (198.51.100.1) and port (53136) on the gateway that
    packets are being translated to.
</dl>

<dl>
<dt>198.51.100.10:22
<dd>The IP address (198.51.100.10) and the port (22) that the internal
    machine is connecting to.
</dl>

<dl>

<dt>TIME_WAIT:TIME_WAIT
<dd>This indicates what state PF believes the TCP connection to be in.
</dl>
